/*
 * Ingenic IMP SDK emulator test.
 *
 * Copyright (C) 2014 Ingenic Semiconductor Co.,Ltd
 * Author: Zoro <ykli@ingenic.cn>
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <CUnit/CUnit.h>
#include <CUnit/Automated.h>
#include <CUnit/Basic.h>
#include <CUnit/Console.h>

#include <unistd.h>

#include <imp/imp_log.h>
#include <imp/imp_common.h>
#include <imp/imp_system.h>
#include <imp/imp_framesource.h>
#include <imp/imp_isp.h>
#include <imp/imp_encoder.h>

#include <system/system.h>
#include <system/vbm.h>
#include <emulator/imp_emu_framesource.h>

#define TAG "EncoderTest"

#define SENSOR_FRAME_RATE		8
#define SENSOR_WIDTH			1280
#define SENSOR_HEIGHT			720
#define MAX_FRAMES_TO_SAVE		10
#define FILE_NAME_LEN	50
#define POLLING_TIMEOUT			(50000)

typedef void *(*GetStreamFunc)(void *);

static int ImpSystemInit()
{
	IMP_LOG_DBG(TAG, "ImpSystemInit\n");
	IMP_System_Init();

	return 0;
}

static int ImpSystemExit()
{
	IMP_LOG_DBG(TAG, "ImpSystemExit\n");
	IMP_System_Exit();
	return 0;
}

static int save_stream(int fd, int chnNum, IMPEncoderStream *stream)
{
	int ret, i, nr_pack = stream->packCount;

	for (i = 0; i < nr_pack; i++) {
		ret = write(fd, (void *)stream->pack[i].virAddr,
					stream->pack[i].length);
		if (ret != stream->pack[i].length) {
			IMP_LOG_ERR(TAG, "channel%d stream write error:%s\n", chnNum, strerror(errno));
			return -1;
		}
	}

	return 0;
}

static int ImpFrameSourceInit()
{
	int ret;

	/* Configure channel 0 attr */
	IMPFSChnAttr imp_chn_attr;
	imp_chn_attr.pixFmt = PIX_FMT_NV12;
	imp_chn_attr.outFrmRate = SENSOR_FRAME_RATE;
	imp_chn_attr.nrVBs = 4;

	imp_chn_attr.crop.enable = 1;
	imp_chn_attr.crop.top = 128;
	imp_chn_attr.crop.left = 128;
	imp_chn_attr.crop.width = SENSOR_WIDTH;
	imp_chn_attr.crop.height = SENSOR_HEIGHT;

	imp_chn_attr.scaler.enable = 0;

	imp_chn_attr.picWidth = SENSOR_WIDTH;
	imp_chn_attr.picHeight = SENSOR_HEIGHT;

	/* create channel 0*/
	ret = IMP_EmuFrameSource_CreateChn(0, &imp_chn_attr);
	if(ret < 0){
		IMP_LOG_ERR(TAG, "IMP_EmuFrameSource_CreateChn(chn0) error!\n");
		return -1;
	}

	/* Check channel 0 attr */
	IMPFSChnAttr imp_chn_attr_check;
	ret = IMP_EmuFrameSource_GetChnAttr(0, &imp_chn_attr_check);
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "IMP_EmuFrameSource_GetChnAttr(1) error: %d\n", ret);
		return -1;
	}
	IMP_LOG_DBG(TAG, "FS Chn0 ChnAttr.picWidth=%d\n", imp_chn_attr_check.picWidth);
	IMP_LOG_DBG(TAG, "FS Chn0 ChnAttr.picHeight=%d\n", imp_chn_attr_check.picHeight);
	IMP_LOG_DBG(TAG, "FS Chn0 ChnAttr.pixFmt=%d\n", imp_chn_attr_check.pixFmt);
	IMP_LOG_DBG(TAG, "FS Chn0 ChnAttr.outFrmRate=%d\n", imp_chn_attr_check.outFrmRate);

	/* Configure channel 1 attr */
//	imp_chn_attr.pixFmt = PIX_FMT_YUYV422;
	imp_chn_attr.pixFmt = PIX_FMT_NV12;
	imp_chn_attr.outFrmRate = SENSOR_FRAME_RATE;
	imp_chn_attr.nrVBs = 4;

	imp_chn_attr.crop.enable = 1;
	imp_chn_attr.crop.top = 128;
	imp_chn_attr.crop.left = 128;
	imp_chn_attr.crop.width = SENSOR_WIDTH;
	imp_chn_attr.crop.height = SENSOR_HEIGHT;

	imp_chn_attr.scaler.enable = 1;
	imp_chn_attr.scaler.outwidth = SENSOR_WIDTH/2;
	imp_chn_attr.scaler.outheight = SENSOR_HEIGHT/2;

	imp_chn_attr.picWidth = SENSOR_WIDTH/2;
	imp_chn_attr.picHeight = SENSOR_HEIGHT/2;
	/* create channel 1*/
	ret = IMP_EmuFrameSource_CreateChn(1, &imp_chn_attr);
	if(ret < 0){
		IMP_LOG_ERR(TAG, "IMP_EmuFrameSource_CreateChn(chn1) error!\n");
		return -1;
	}

	/* Check channel 1 attr */
	ret = IMP_EmuFrameSource_GetChnAttr(1, &imp_chn_attr_check);
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "IMP_EmuFrameSource_GetChnAttr(1) error: %d\n", ret);
		return -1;
	}
	IMP_LOG_DBG(TAG, "FS Chn1 ChnAttr.picWidth=%d\n", imp_chn_attr_check.picWidth);
	IMP_LOG_DBG(TAG, "FS Chn1 ChnAttr.picHeight=%d\n", imp_chn_attr_check.picHeight);
	IMP_LOG_DBG(TAG, "FS Chn1 ChnAttr.pixFmt=%d\n", imp_chn_attr_check.pixFmt);
	IMP_LOG_DBG(TAG, "FS Chn1 ChnAttr.outFrmRate=%d\n", imp_chn_attr_check.outFrmRate);

	IMP_LOG_DBG(TAG, "%s(): OK.\n", __func__);

	return 0;
}

static int ImpFrameSourceExit()
{
	IMP_EmuFrameSource_DestroyChn(0);
	IMP_EmuFrameSource_DestroyChn(1);
	return 0;
}

static int ImpStreamOn()
{
	int ret = 0;
	/* Enable channels */
	ret = IMP_EmuFrameSource_EnableChn(0);
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "IMP_EmuFrameSource_EnableChn(0) error: %d\n", ret);
		return -1;
	}

	ret = IMP_EmuFrameSource_EnableChn(1);
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "IMP_EmuFrameSource_EnableChn(1) error: %d\n", ret);
		return -1;
	}

	return 0;
}

static int ImpStreamOff()
{
	int ret = 0;
	/* Disable channels */
	ret = IMP_EmuFrameSource_DisableChn(0);
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "IMP_EmuFrameSource_EnableChn(0) error: %d\n", ret);
		return -1;
	}

	ret = IMP_EmuFrameSource_DisableChn(1);
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "IMP_EmuFrameSource_EnableChn(1) error: %d\n", ret);
		return -1;
	}
	return 0;
}

static int encoder_init_h264(void)
{
	int ret, i;

	/* Init Channel attr */
	IMPEncoderAttr enc_attr;
	memset(&enc_attr, 0, sizeof(IMPEncoderAttr));
	enc_attr.enType = PT_H264;
	enc_attr.bufSize = 0;
	enc_attr.profile = 0;
	enc_attr.picWidth = SENSOR_WIDTH;
	enc_attr.picHeight = SENSOR_HEIGHT;

	IMPEncoderRcAttr rc_attr;
	memset(&rc_attr, 0, sizeof(IMPEncoderRcAttr));
#ifdef CONFIG_X264
	rc_attr.rcMode = ENC_RC_MODE_VBR;
	rc_attr.attrH264Vbr.maxGop = SENSOR_FRAME_RATE;
	rc_attr.attrH264Vbr.inFrmRate = SENSOR_FRAME_RATE;
	rc_attr.attrH264Vbr.outFrmRate = SENSOR_FRAME_RATE;
	rc_attr.attrH264Vbr.maxBitRate = 1800;
	rc_attr.attrH264Vbr.maxQp = 34;
	rc_attr.attrH264Vbr.minQp = 26;
#else
	rc_attr.rcMode = ENC_RC_MODE_CBR;
	rc_attr.attrH264Cbr.maxGop = SENSOR_FRAME_RATE * 3;
	rc_attr.attrH264Cbr.inFrmRate = SENSOR_FRAME_RATE;
	rc_attr.attrH264Cbr.outFrmRate = SENSOR_FRAME_RATE;
	rc_attr.attrH264Cbr.outBitRate = 300;
	rc_attr.attrH264Cbr.maxQp = 34;
	rc_attr.attrH264Cbr.minQp = 24;
	rc_attr.attrH264Cbr.iBiasLvl = 0;
	rc_attr.attrH264Cbr.frmQPStep = 7;
	rc_attr.attrH264Cbr.gopQPStep = 7;
	rc_attr.attrH264Cbr.adaptiveMode = false;
	rc_attr.attrH264Cbr.gopRelation = false;
#endif

	IMPEncoderCHNAttr channel_attr;
	channel_attr.encAttr = enc_attr;
	channel_attr.rcAttr = rc_attr;

	for (i = 0; i < 2; i++) {
		/* Creat Encoder Group */
		ret = IMP_Encoder_CreateGroup(i);
		if (ret < 0) {
			IMP_LOG_ERR(TAG, "IMP_Encoder_CreateGroup(%d) error: %d\n", i, ret);
			return -1;
		}

		enc_attr.picWidth = SENSOR_WIDTH >> !!i;
		enc_attr.picHeight = SENSOR_HEIGHT >> !!i;
		channel_attr.encAttr = enc_attr;

		/* Create Channel */
		ret = IMP_Encoder_CreateChn(i, &channel_attr);
		if (ret < 0) {
			IMP_LOG_ERR(TAG, "IMP_Encoder_CreateChn(%d) error: %d\n", i, ret);
			return -1;
		}

		/* Resigter Channel */
		ret = IMP_Encoder_RegisterChn(i, i);
		if (ret < 0) {
			IMP_LOG_ERR(TAG, "IMP_Encoder_RegisterChn(%d, %d) error: %d\n", i, i, ret);
			return -1;
		}
	}

	return 0;
}

static int encoder_exit_h264(void)
{
	int i = 0;

	for (i = 0; i < 2; i++) {
		IMP_Encoder_UnRegisterChn(i);
		IMP_Encoder_DestroyChn(i);
		IMP_Encoder_DestroyGroup(i);
	}

	IMP_LOG_DBG(TAG, "exit OK\n");

	return 0;
}

static int encoder_init_jpeg(void)
{
	int ret, i;

	/* Init Channel attr */
	IMPEncoderAttr enc_attr;
	memset(&enc_attr, 0, sizeof(IMPEncoderAttr));
	enc_attr.enType = PT_JPEG;
	enc_attr.bufSize = 0;
	enc_attr.profile = 0;
	enc_attr.picWidth = SENSOR_WIDTH;
	enc_attr.picHeight = SENSOR_HEIGHT;

	IMPEncoderCHNAttr channel_attr;
	memset(&channel_attr, 0, sizeof(IMPEncoderCHNAttr));
	channel_attr.encAttr = enc_attr;

	for (i = 0; i < 2; i++) {
		/* Creat Encoder Group */
		ret = IMP_Encoder_CreateGroup(i);
		if (ret < 0) {
			IMP_LOG_ERR(TAG, "IMP_Encoder_CreateGroup(%d) error: %d\n", i, ret);
			return -1;
		}

		/* Create Channel */
		enc_attr.picWidth = SENSOR_WIDTH >> !!i;
		enc_attr.picHeight = SENSOR_HEIGHT >> !!i;
		channel_attr.encAttr = enc_attr;
		ret = IMP_Encoder_CreateChn(i, &channel_attr);
		if (ret < 0) {
			IMP_LOG_ERR(TAG, "IMP_Encoder_CreateChn(%d) error: %d\n", i, ret);
			return -1;
		}

		/* Resigter Channel */
		ret = IMP_Encoder_RegisterChn(i, i);
		if (ret < 0) {
			IMP_LOG_ERR(TAG, "IMP_Encoder_RegisterChn(%d, %d) error: %d\n", i, i, ret);
			return -1;
		}
	}

	return 0;
}

static int encoder_exit_jpeg(void)
{
	int i = 0;

	for (i = 0; i < 2; i++) {
		IMP_Encoder_UnRegisterChn(i);
		IMP_Encoder_DestroyChn(i);
		IMP_Encoder_DestroyGroup(i);
	}

	IMP_LOG_DBG(TAG, "exit OK\n");

	return 0;
}

static int open_stream_file(char *file_path)
{
	int fd = open(file_path, O_RDWR | O_CREAT | O_TRUNC, 0664);
	return fd;
}

static void close_stream_file(int fd)
{
	close(fd);
}

static void dump_enc_chn_stat(IMPEncoderCHNStat *chn_stat)
{
	IMP_LOG_DBG(TAG, "Dump Encoder Channel stat:\n");
	IMP_LOG_DBG(TAG, "\tregistered:%d\n", chn_stat->registered);
	IMP_LOG_DBG(TAG, "\tleftPics:%u\n", chn_stat->leftPics);
	IMP_LOG_DBG(TAG, "\tleftStreamBytes:%u\n", chn_stat->leftStreamBytes);
	IMP_LOG_DBG(TAG, "\tleftStreamFrames:%u\n", chn_stat->leftStreamFrames);
	IMP_LOG_DBG(TAG, "\tcurPacks:%u\n", chn_stat->curPacks);
}

static void *do_get_channel_stream(void *arg)
{
	int ret, chnNum = (int)arg;
	char stream_file_name[FILE_NAME_LEN];

	ret = IMP_Encoder_StartRecvPic(chnNum);
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "IMP_Encoder_StartRecvPic(%d) failed\n", chnNum);
		return (void *)-1;
	}

	sprintf(stream_file_name, "/tmp/channel%d_stream.h264", chnNum);
	int i, stream_fd = open_stream_file(stream_file_name);

	for (i = 0; i < MAX_FRAMES_TO_SAVE; i++) {
		ret = IMP_Encoder_PollingStream(chnNum, POLLING_TIMEOUT);
		if (ret < 0) {
			IMP_LOG_ERR(TAG, "Polling channel%d stream timeout\n");
			return (void *)-1;
		}

		IMPEncoderStream stream;
		IMPEncoderCHNStat chn_stat;
		IMP_Encoder_Query(chnNum, &chn_stat);
		stream.packCount = chn_stat.curPacks;

		dump_enc_chn_stat(&chn_stat);

		ret = IMP_Encoder_GetStream(chnNum, &stream, 1);
		if (ret < 0) {
			IMP_LOG_ERR(TAG, "IMP_Encoder_GetStream(%d) failed\n", chnNum);
			return (void *)-1;
		}

		/* Dump stream */
		IMP_LOG_DBG(TAG, "chnNum=%d, stream seq=%d packCount=%d\n",
					chnNum, stream.seq, stream.packCount);

		ret = save_stream(stream_fd, chnNum, &stream);
		if (ret < 0) {
			close(stream_fd);
			return (void *)ret;
		}

		IMP_Encoder_ReleaseStream(chnNum, &stream);
	}

	close_stream_file(stream_fd);

	ret = IMP_Encoder_StopRecvPic(chnNum);
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "IMP_Encoder_StopRecvPic(%d) failed\n", chnNum);
		return (void *)-1;
	}

	return (void *)0;
}

static void *do_get_channel_snap(void *arg)
{
	int ret, chnNum = (int)arg;
	char snap_file_name[FILE_NAME_LEN];

	ret = IMP_Encoder_StartRecvPic(chnNum);
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "IMP_Encoder_StartRecvPic(%d) failed\n", chnNum);
		return (void *)-1;
	}

	ret = IMP_Encoder_PollingStream(chnNum, POLLING_TIMEOUT);
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "Polling stream timeout\n");
		return (void *)-1;
	}

	IMPEncoderStream stream;
	IMPEncoderCHNStat chn_stat;
	IMP_Encoder_Query(chnNum, &chn_stat);
	stream.packCount = chn_stat.curPacks;

	dump_enc_chn_stat(&chn_stat);

	ret = IMP_Encoder_GetStream(chnNum, &stream, 1);
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "IMP_Encoder_GetStream(%d) failed\n", chnNum);
		return (void *)-1;
	}

	/* Dump stream */
	IMP_LOG_DBG(TAG, "channel=%d, stream seq=%d packCount=%d\n",
				chnNum, stream.seq, stream.packCount);

	sprintf(snap_file_name, "/tmp/channel%d_snap.jpg", chnNum);
	int stream_fd = open_stream_file(snap_file_name);

	ret = save_stream(stream_fd, chnNum, &stream);
	if (ret < 0) {
		close_stream_file(stream_fd);
		return (void *)ret;
	}

	IMP_Encoder_ReleaseStream(chnNum, &stream);

	close_stream_file(stream_fd);

	ret = IMP_Encoder_StopRecvPic(chnNum);
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "IMP_Encoder_StopRecvPic(%d) failed\n", chnNum);
		return (void *)-1;
	}

	return (void *)0;
}

static int do_get_stream(GetStreamFunc getStreamFunc[2])
{
	int ret = -1, i;
	pthread_t tid[2] = {-1, -1};

	for (i = 0; i < 2; i++) {
		if (pthread_create(&tid[i], NULL, getStreamFunc[i], (void *)i)) {
			IMP_LOG_ERR(TAG, "pthread_create get channel %d stream failed\n", i);
			goto err_pthread_create;
		}
	}

	for (i = 0; i < 2; i++) {
		if (tid[i] >= 0) {
			if ((pthread_join(tid[i], (void **)&ret) < 0) || (ret < 0)) {
				IMP_LOG_ERR(TAG, "pthread_join channel %d stream failed\n", i, ret);
				goto err_pthread_join;
			}
			tid[i] = -1;
		}
	}

	return 0;

err_pthread_join:
err_pthread_create:
	for (i = 0; i < 2; i++) {
		if (tid[i] >= 0) {
			pthread_cancel(tid[i]);
		}
	}

	return -1;
}

static int ImpBindModules()
{
	IMPCell framesource_chn0 = {DEV_ID_EMU_FS, 0, 0};
	IMPCell framesource_chn1 = {DEV_ID_EMU_FS, 1, 0};
	IMPCell encoder0 = { DEV_ID_ENC, 0, 0};
	IMPCell encoder1 = { DEV_ID_ENC, 1, 1};

	IMP_System_Bind(&framesource_chn0, &encoder0);
	IMP_System_Bind(&framesource_chn1, &encoder1);

	system_bind_dump();

	/* Bind modules here. */
	return 0;
}

static int ImpUnBindModules()
{
	IMPCell framesource_chn0 = {DEV_ID_EMU_FS, 0, 0};
	IMPCell framesource_chn1 = {DEV_ID_EMU_FS, 1, 0};
	IMPCell encoder0 = { DEV_ID_ENC, 0, 0};
	IMPCell encoder1 = { DEV_ID_ENC, 1, 1};

	IMP_System_UnBind(&framesource_chn0, &encoder0);
	IMP_System_UnBind(&framesource_chn1, &encoder1);

	return 0;
}

static void test_h264_stream() /* SYS+emuFS+ENC+h264 */
{
	int ret;

	ret = ImpSystemInit();
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "IMP system init failed\n");
		goto err_ImpSystemInit;
	}

	ret = ImpFrameSourceInit();
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "IMP ISP init failed\n");
		goto err_ImpFrameSourceInit;
	}

	ret = encoder_init_h264();
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "IMP Encoder init failed\n");
		goto err_encoder_init_h264;
	}

	ret = ImpBindModules();
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "IMP Encoder init failed\n");
		goto err_ImpBindModules;
	}

	ret = ImpStreamOn();
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "IMP stream on error\n");
		goto err_ImpStreamOn;
	}
	GetStreamFunc getStreamFunc[2] = {do_get_channel_stream, do_get_channel_stream};
	ret = do_get_stream(getStreamFunc);
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "get stream failed\n");
		goto err_do_get_stream;
	}

	ret = ImpStreamOff();
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "IMP stream off error\n");
		goto err_ImpStreamOn;
	}

	ImpUnBindModules();

	ret = encoder_exit_h264();
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "encoder_exit_h264 failed\n");
		goto err_ImpStreamOn;
	}

	ImpFrameSourceExit();
	ImpSystemExit();

	CU_PASS();

	return;

err_do_get_stream:
	ImpStreamOff();
err_ImpStreamOn:
	ImpUnBindModules();
err_ImpBindModules:
	encoder_exit_h264();
err_encoder_init_h264:
	ImpFrameSourceExit();
err_ImpFrameSourceInit:
	ret = IMP_System_Exit();
	if (ret < 0)
		IMP_LOG_ERR(TAG, "IMP_System_Exit() failed\n");
err_ImpSystemInit:
	CU_FAIL();
}

static void test_jpeg_snap() /* SYS+emuFS+ENC+jpeg */
{
	int ret;

	ret = ImpSystemInit();
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "IMP system init failed\n");
		goto err_ImpSystemInit;
	}

	ret = ImpFrameSourceInit();
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "IMP ISP init failed\n");
		goto err_ImpFrameSourceInit;
	}

	ret = encoder_init_jpeg();
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "IMP Encoder init failed\n");
		goto err_encoder_init_jpeg;
	}

	ret = ImpBindModules();
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "IMP Encoder init failed\n");
		goto err_ImpBindModules;
	}

	ret = ImpStreamOn();
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "IMP stream on error\n");
		goto err_ImpStreamOn;
	}
	GetStreamFunc getStreamFunc[2] = {do_get_channel_snap, do_get_channel_snap};
	ret = do_get_stream(getStreamFunc);
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "get stream failed\n");
		goto err_do_get_stream;
	}

	ret = ImpStreamOff();
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "IMP stream off error\n");
		goto err_ImpStreamOn;
	}

	ImpUnBindModules();

	ret = encoder_exit_jpeg();
	if (ret < 0) {
		IMP_LOG_ERR(TAG, "encoder_exit_jpeg failed\n");
		goto err_ImpStreamOn;
	}

	ImpFrameSourceExit();
	ImpSystemExit();

	CU_PASS();

	return;

err_do_get_stream:
	ImpStreamOff();
err_ImpStreamOn:
	ImpUnBindModules();
err_ImpBindModules:
	encoder_exit_jpeg();
err_encoder_init_jpeg:
	ImpFrameSourceExit();
err_ImpFrameSourceInit:
	ret = IMP_System_Exit();
	if (ret < 0)
		IMP_LOG_ERR(TAG, "IMP_System_Exit() failed\n");
err_ImpSystemInit:
	CU_FAIL();
}

CU_TestInfo EncoderTest[] = {
	{"SYS+emuFS+ENC+h264", test_h264_stream},
	{"SYS+emuFS+ENC+jpeg", test_jpeg_snap},
	CU_TEST_INFO_NULL,
};
